using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using Rhizome.Contract;
using Rhizome.Internal;

namespace Rhizome.Model
{	
    public sealed class CriterionTypeConverter : TypeConverter
    {

        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
        {
            return sourceType == typeof (string);
        }

        public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
        {
        	if (value == null) throw new ArgumentNullException("value");
        	
			var s = ((string) value).Trim();
			if (s.IsNullOrEmpty()) return null;

        	var ors =
        		(from o in s.Split('|')
        		let or = o.Trim()
        		where or.IsNullOrEmpty() == false
        		select
        			new
        				{
							Ands =
        						(from a in or.Split(new []{'&','*'}, StringSplitOptions.RemoveEmptyEntries)
 								let and = a.Trim()
								where and.IsNullOrEmpty() == false
								select ParseCriterion(and)).ToArray()
        				}).ToArray();

        	switch (ors.Length)
        	{
				case 0:
        			return null;
				case 1:
        			return CreateAndCriterion(ors[0].Ands);

				default:
        			var or = new OrCriterion();
        			foreach (var o in ors)
        				or.Criterions.Add(CreateAndCriterion(o.Ands));
        			
						return or;
        	}

        }

    	private static ICriterion CreateAndCriterion(IEnumerable<ICriterion> criteria)
    	{
    		switch (criteria.Count())
    		{
				case 0:
    				return null;
    			case 1:
    				return criteria.First();
				
				default:
    				var and = new AndCriterion();
    				foreach (var c in criteria) and.Criterions.Add(c);
    				return and;
    		}
    	}


        /// <summary>
        /// Converts specific string to criterion
        /// </summary>
        /// <param name="s">string to parse</param>
        /// <returns>"basic" criterion: IfTrue, IfNotTrue, IfEquals, IfNotEquals, IfMoreThen, IfMoreThenOrEquals, IfLessThen, IfLessThenOrEquals</returns>
        internal static ICriterion ParseCriterion(string s)
		{
			s = s.Replace(" ", string.Empty).Replace("\t", string.Empty);

			if (s.StartsWith("!"))
				return new IfNotTrue(s.TrimStart('!'));


			if (s.Contains("!="))
			{
				var vals = s.Split(new[] { '!', '=' }, StringSplitOptions.RemoveEmptyEntries);
				var p = vals[0];
				var v = vals[1].ConvertValue();

				var type = v.GetType();
				if (type == typeof(bool))
				{
					var b = (bool)v;
					return b ? (ICriterion)new IfNotTrue(p) : new IfTrue(p);
				}

				if (type == typeof(int)) return new IfNotEquals<int>(p, (int)v);
				if (type == typeof(double)) return new IfNotEquals<double>(p, (double)v);
				return new IfNotEquals(p, v);
			}

			if (s.Contains(">="))
			{
				var vals = s.Split(new[] { '>', '=' }, StringSplitOptions.RemoveEmptyEntries);
				var p = vals[0];
				var v = vals[1].ConvertValue();

				var type = v.GetType();
				if (type == typeof(int)) return new IfMoreThenOrEquals<int>(p, (int)v);
				if (type == typeof(double)) return new IfMoreThenOrEquals<double>(p, (double)v);
				return new IfMoreThenOrEquals<IComparable>(p, (IComparable)v);
			}

			if (s.Contains(">"))
			{
				var vals = s.Split(new[] { '>' }, StringSplitOptions.RemoveEmptyEntries);
				var p = vals[0];
				var v = vals[1].ConvertValue();

				var type = v.GetType();
				if (type == typeof(int)) return new IfMoreThen<int>(p, (int)v);
				if (type == typeof(double)) return new IfMoreThen<double>(p, (double)v);
				return new IfMoreThen<IComparable>(p, (IComparable)v);
			}

			if (s.Contains("<="))
			{
				var vals = s.Split(new[] { '<', '=' }, StringSplitOptions.RemoveEmptyEntries);
				var p = vals[0];
				var v = vals[1].ConvertValue();

				var type = v.GetType();
				if (type == typeof(int)) return new IfLessThenOrEquals<int>(p, (int)v);
				if (type == typeof(double)) return new IfLessThenOrEquals<double>(p, (double)v);
				return new IfLessThenOrEquals<IComparable>(p, (IComparable)v);
			}

			if (s.Contains("<"))
			{
				var vals = s.Split(new[] { '<' }, StringSplitOptions.RemoveEmptyEntries);
				var p = vals[0];
				var v = vals[1].ConvertValue();

				var type = v.GetType();
				if (type == typeof(int)) return new IfLessThen<int>(p, (int)v);
				if (type == typeof(double)) return new IfLessThen<double>(p, (double)v);
				return new IfLessThen<IComparable>(p, (IComparable)v);
			}

			if (s.Contains("="))
			{
				var vals = s.Split(new[] { '=' }, StringSplitOptions.RemoveEmptyEntries);
				var p = vals[0];
				var v = vals[1].ConvertValue();
				var type = v.GetType();
				if (type == typeof(bool))
				{
					var b = (bool)v;
					return b ? (ICriterion)new IfTrue(p) : new IfNotTrue(p);
				}

				if (type == typeof(int)) return new IfEquals<int>(p, (int)v);
				if (type == typeof(double)) return new IfEquals<double>(p, (double)v);
				return new IfEquals(p, v);
			}

			return new IfTrue(s);
		}
    }
}